﻿using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;

namespace xu.MinFileLogger
{
    public static class Logger
    {
        private readonly static ReaderWriterLockSlim LogWriteLock = new ReaderWriterLockSlim();

        private static string FilePath { get; set; } = AppDomain.CurrentDomain.BaseDirectory;

        private static readonly Channel<LogEntity> LogChannel = Channel.CreateUnbounded<LogEntity>();

        static Logger()
        {
            Task.Factory.StartNew(async () =>
            {
                while (await LogChannel.Reader.WaitToReadAsync())
                {
                    if (LogChannel.Reader.TryRead(out LogEntity log))
                    {
                        Writer(log);
                    }
                    else
                    {
                        Thread.Sleep(10);
                    }
                }
            });
        }

        public static void Init(this string filePath)
        {
            FilePath = filePath;
            if (!Directory.Exists(FilePath)) { Directory.CreateDirectory(FilePath); }
        }

        public static void LogTrace(this string content) => Log(LogLevel.TRACE, content);

        public static void LogDebug(this string content) => Log(LogLevel.DEBUG, content);

        public static void LogInfo(this string content) => Log(LogLevel.INFO, content);

        public static void LogWarn(this string content) => Log(LogLevel.WARN, content);

        public static void LogError(this string content) => Log(LogLevel.ERROR, content);

        public static void LogFatal(this string content) => Log(LogLevel.FATAL, content);

        public static void LogFatal<TClass>(this string content) where TClass : class => Log<TClass>(LogLevel.FATAL, content);

        private static void Log(LogLevel level, string logMessage)
        {
            var stackFrame = FindStackFrame();
            var methodBase = GetCallingMethodBase(stackFrame);
            var callingMethod = methodBase.Name;
            var callingClass = methodBase.ReflectedType.FullName;
            var lineNumber = stackFrame.GetFileLineNumber();

            LogChannel.Writer.TryWrite(new LogEntity()
            {
                LogMsg = logMessage,
                LevelCode = level,
                Date = DateTime.Now,
                CallingClass = callingClass,
                CallingMethod = callingMethod,
                LineNumber = lineNumber
            });
        }

        private static void Log<TClass>(LogLevel level, string logMessage) where TClass : class
        {
            var stackFrame = FindStackFrame();
            var methodBase = GetCallingMethodBase(stackFrame);
            var callingMethod = methodBase.Name;
            var callingClass = typeof(TClass).FullName;
            var lineNumber = stackFrame.GetFileLineNumber();

            LogChannel.Writer.TryWrite(new LogEntity()
            {
                LogMsg = logMessage,
                LevelCode = level,
                Date = DateTime.Now,
                CallingClass = callingClass,
                CallingMethod = callingMethod,
                LineNumber = lineNumber
            });
        }

        private static void Writer(LogEntity log)
        {
            string logText = string.Format("{0} {1} {2}.{3} Line:{4} {5}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"),
                log.LevelCode.ToString(), log.CallingClass, log.CallingMethod, log.LineNumber, log.LogMsg);

            try
            {
                string filePath = Path.Combine(FilePath, $"log\\{DateTime.Now:yyyy-MM-dd}");

                if (!Directory.Exists(filePath)) { Directory.CreateDirectory(filePath); }

                LogWriteLock.EnterWriteLock();

                using (StreamWriter write = File.AppendText(Path.Combine(filePath, $"{DateTime.Now:yyyy-MM-dd-HH}-{log.LevelCode}.log")))
                {
                    write.WriteLine(logText);
                    write.Close();
                }
            }
            catch (Exception)
            {
            }
            finally
            {
                LogWriteLock.ExitWriteLock();
            }
        }

        private static MethodBase GetCallingMethodBase(StackFrame stackFrame)
        {
            return stackFrame == null ? MethodBase.GetCurrentMethod() : stackFrame.GetMethod();
        }

        private static readonly string[] Methods = { "Log", "LogTrace", "LogDebug", "LogInfo", "LogWarn", "LogError", "LogFatal" };

        private static StackFrame FindStackFrame()
        {
            StackTrace stackTrace = new StackTrace();
            for (var i = 0; i < stackTrace.GetFrames().Count(); i++)
            {
                MethodBase methodBase = stackTrace.GetFrame(i).GetMethod();
                string name = MethodBase.GetCurrentMethod().Name;
                //if (!methodBase.Name.Equals("Log") && !methodBase.Name.Equals(name) && !methodBase.Name.Equals("LogFatal"))
                //{
                //    return new StackFrame(i, true);
                //}
                if (!methodBase.Name.Equals(name) && !Methods.Contains(methodBase.Name))
                {
                    return new StackFrame(i, true);
                }
            }
            return null;
        }
    }

    /// <summary>
    /// 日志类型
    /// </summary>
    internal enum LogLevel
    {
        /// <summary>
        /// 追踪
        /// </summary>
        TRACE = 1001,

        /// <summary>
        /// 调试
        /// </summary>
        DEBUG = 1002,

        /// <summary>
        /// 普通
        /// </summary>
        INFO = 1003,

        /// <summary>
        /// 现潜在错误
        /// </summary>
        WARN = 1004,

        /// <summary>
        /// 发生错误事件，但仍然不影响系统的继续运行
        /// </summary>
        ERROR = 1005,

        /// <summary>
        /// 严重的错误事件将会导致应用程序的退出
        /// </summary>
        FATAL = 1006
    }

    internal class LogEntity
    {
        /// <summary>
        /// 日志等级
        /// </summary>
        public LogLevel LevelCode { get; set; }

        /// <summary>
        /// 日志信息
        /// </summary>
        public string LogMsg { get; set; }

        /// <summary>
        /// 日志产生时间
        /// </summary>
        public DateTime Date { get; set; }

        public string CallingClass { get; set; }
        public string CallingMethod { get; set; }
        public int LineNumber { get; set; }
    }
}
